using System;
using System.Linq;
using System.Reflection;

using Cookbook.Common;
using Cookbook.Services.Core.Commands;
using Cookbook.Services.Core.Handlers;
using Cookbook.Services.Core.Persistency.NHibernate;

using FluentAssertions;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Cooking.Services.Specs.Handlers
{
    [TestClass]
    public class MapsToAggregateRootMethodAttributeSpecs
    {
        [TestMethod]
        public void When_any_command_is_not_properly_mapped_it_should_throw()
        {
            //-----------------------------------------------------------------------------------------------------------
            // Arrange
            //-----------------------------------------------------------------------------------------------------------
            var commandTypes = 
                from type in typeof(VersionedCommand).Assembly.GetTypes()
                where type.HasAttribute<MapsToAggregateRootMethodAttribute>()
                select type;

            //-----------------------------------------------------------------------------------------------------------
            // Act / Assert
            //-----------------------------------------------------------------------------------------------------------
            foreach (var commandType in commandTypes)
            {
                When_command_is_mapped_to_non_existing_entity_method_it_should_throw(commandType);
                When_command_properties_do_not_map_to_the_entity_method_parameters_it_should_throw(commandType);
            }
        }

        public void When_command_is_mapped_to_non_existing_entity_method_it_should_throw(Type commandType)
        {
            //-----------------------------------------------------------------------------------------------------------
            // Arrange
            //-----------------------------------------------------------------------------------------------------------
            var mappingAttribute = commandType.FindAttribute<MapsToAggregateRootMethodAttribute>();

            //-----------------------------------------------------------------------------------------------------------
            // Act
            //-----------------------------------------------------------------------------------------------------------
            Action action = () => mappingAttribute.GetMethod();

            //-----------------------------------------------------------------------------------------------------------
            // Assert
            //-----------------------------------------------------------------------------------------------------------
            action.ShouldNotThrow("because command {0} should be properly mapped to entity {1}", 
                commandType.Name, mappingAttribute.AggregateRootType.Name);
        }
        
        public void When_command_properties_do_not_map_to_the_entity_method_parameters_it_should_throw(Type commandType)
        {
            //-----------------------------------------------------------------------------------------------------------
            // Arrange
            //-----------------------------------------------------------------------------------------------------------
            var mappingAttribute = commandType.FindAttribute<MapsToAggregateRootMethodAttribute>();

            MethodInfo methodInfo = mappingAttribute.GetMethod();

            var properties = commandType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            //-----------------------------------------------------------------------------------------------------------
            // Act
            //-----------------------------------------------------------------------------------------------------------)
            foreach (var parameterInfo in methodInfo.GetParameters())
            {
                //-----------------------------------------------------------------------------------------------------------
                // Assert
                //-----------------------------------------------------------------------------------------------------------
                bool matches = properties.Any(pi =>
                    pi.Name.Equals(parameterInfo.Name, StringComparison.InvariantCultureIgnoreCase) ||
                        pi.Name.Equals(parameterInfo.Name + DatabaseConstants.IdentitySuffix,
                            StringComparison.InvariantCultureIgnoreCase));

                matches.Should().BeTrue("{0} must have a property mapped to parameter '{1}'", commandType.Name, parameterInfo.Name);
            }
        }
    }
}